package org.zanata.adapter.xliff;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import com.sun.xml.txw2.output.IndentingXMLStreamWriter;
import org.zanata.common.ContentState;
import org.zanata.rest.dto.extensions.comment.SimpleComment;
import org.zanata.rest.dto.extensions.gettext.TextFlowExtension;
import org.zanata.rest.dto.resource.Resource;
import org.zanata.rest.dto.resource.TextFlow;
import org.zanata.rest.dto.resource.TextFlowTarget;
import org.zanata.rest.dto.resource.TranslationsResource;
import org.zanata.util.PathUtil;

public class XliffWriter extends XliffCommon {
    /**
     * Write document header with XML, xliff, file and body tag
     *
     * @param writer
     * @param doc
     * @param targetLocale
     *            (use hyphen, not underscore)
     * @throws XMLStreamException
     */
    private static void writeHeader(XMLStreamWriter writer,
            Resource doc, String targetLocale) throws XMLStreamException {
        // XML tag
        writer.writeStartDocument("utf-8", "1.0");
        writer.writeComment("XLIFF document generated by Zanata. Visit http://zanata.org for more infomation.");
        writer.writeCharacters("\n");
        // XLiff tag
        writer.writeStartElement("xliff");
        writer.writeNamespace("", "urn:oasis:names:tc:xliff:document:1.1");
        writer.writeNamespace("xyz", "urn:appInfo:Items");
        writer.writeNamespace("xsi",
                "http://www.w3.org/2001/XMLSchema-instance");
        writer.writeAttribute(
                "xsi:schemaLocation",
                "urn:oasis:names:tc:xliff:document:1.1 http://www.oasis-open.org/committees/xliff/documents/xliff-core-1.1.xsd");
        writer.writeAttribute("version", "1.1");

        // file tag
        writer.writeStartElement(ELE_FILE);
        writer.writeAttribute(ATTRI_SOURCE_LANGUAGE, doc.getLang().getId());
        writer.writeAttribute(ATTRI_DATATYPE, "plaintext");
        writer.writeAttribute(ATTRI_ORIGINAL, "");
        if (targetLocale != null) {
            writer.writeAttribute(ATTRI_TARGET_LANGUAGE, targetLocale);
        }

        // body tag
        writer.writeStartElement(ELE_BODY);
    }

    private static void writeTransUnits(XMLStreamWriter writer,
            Resource doc, @Nullable TranslationsResource targetDoc,
            boolean createSkeletons, boolean approvedOnly) throws XMLStreamException {
        Map<String, TextFlowTarget> targets = Collections.emptyMap();
        if (targetDoc != null) {
            targets = new HashMap<>();
            for (TextFlowTarget target : targetDoc.getTextFlowTargets()) {
                targets.put(target.getResId(), target);
            }
        }
        for (TextFlow textFlow : doc.getTextFlows()) {
            TextFlowTarget target = targets.get(textFlow.getId());
            if (target == null && !createSkeletons) {
                continue;
            }

            writer.writeStartElement(ELE_TRANS_UNIT);
            writer.writeAttribute(ATTRI_ID, textFlow.getId());
            writeTransUnitSource(writer, textFlow);
            if (target != null) {
                if (usable(target.getState(), approvedOnly)) {
                    writeTransUnitTarget(writer, target);
                }
            }
            writeTransUnitContext(writer, textFlow);
            // end trans-unit tag
            writer.writeEndElement();
        }
    }

    private static boolean usable(ContentState state, boolean approvedOnly) {
        return state.isApproved() ||
                (!approvedOnly && state.isTranslated());
    }

    private static void writeTransUnitSource(XMLStreamWriter writer,
            TextFlow textFlow) throws XMLStreamException {
        writer.writeStartElement(ELE_SOURCE);
        List<String> contents = textFlow.getContents();
        if (contents.size() != 1) {
            throw new RuntimeException(
                    "file format does not support plural forms: resId="
                            + textFlow.getId());
        }
        writer.writeCharacters(contents.get(0));
        // end source tag
        writer.writeEndElement();
    }

    private static void writeTransUnitTarget(XMLStreamWriter writer,
            TextFlowTarget target) throws XMLStreamException {
        writer.writeStartElement(ELE_TARGET);
        List<String> contents = target.getContents();
        if (contents.size() != 1) {
            throw new RuntimeException(
                    "file format does not support plural forms: resId="
                            + target.getResId());
        }
        writer.writeCharacters(contents.get(0));
        // end target tag
        writer.writeEndElement();
    }

    private static void writeTransUnitContext(XMLStreamWriter writer,
            TextFlow textFlow) throws XMLStreamException {
        if (!textFlow.getExtensions(true).isEmpty()) {
            Map<String, ArrayList<String[]>> contextGroupMap =
                    new HashMap<>();

            for (TextFlowExtension textFlowExtension : textFlow.getExtensions()) {
                SimpleComment comment = (SimpleComment) textFlowExtension;
                String[] contextValues = comment.getValue().split(DELIMITER, 3);
                if (!contextGroupMap.containsKey(contextValues[0])) {
                    ArrayList<String[]> list = new ArrayList<String[]>();
                    list.add(new String[] { contextValues[1], contextValues[2] });
                    contextGroupMap.put(contextValues[0], list);
                } else {
                    ArrayList<String[]> list =
                            contextGroupMap.get(contextValues[0]);
                    list.add(new String[] { contextValues[1], contextValues[2] });
                }
            }

            for (Map.Entry<String, ArrayList<String[]>> entry : contextGroupMap
                    .entrySet()) {
                String key = entry.getKey();
                ArrayList<String[]> values = entry.getValue();

                writer.writeStartElement(ELE_CONTEXT_GROUP);
                writer.writeAttribute(ATTRI_NAME, key);

                for (String[] val : values) {
                    writer.writeStartElement(ELE_CONTEXT);
                    writer.writeAttribute(ATTRI_CONTEXT_TYPE, val[0]);
                    writer.writeCharacters(val[1]);
                    // end context
                    writer.writeEndElement();
                }

                // end context-group
                writer.writeEndElement();
            }
        }
    }

    /**
     * Used for writing target file
     *
     * @param baseDir
     * @param doc
     * @param locale
     *            (use hyphen, not underscore)
     * @param targetDoc
     *            may be null
     */
    public static void write(File baseDir, Resource doc, String locale,
            @Nullable TranslationsResource targetDoc, boolean createSkeletons, boolean approvedOnly) {
        File outFile =
                new File(baseDir, doc.getName() + "_"
                        + locale.replace('-', '_') + ".xml");
        writeFile(outFile, doc, locale, targetDoc, createSkeletons, approvedOnly);
    }

    /**
     * Used for writing translation file with given translations map
     *
     * @param doc
     * @param targetDoc
     * @param file
     * @param locale (use hyphen, not underscore)
     */
    public static void writeFile(File file, Resource doc, String locale,
            @Nullable TranslationsResource targetDoc, boolean createSkeletons,
            boolean approvedOnly) {

        try {
            PathUtil.makeParents(file);
        } catch (IOException e) {
            throw new RuntimeException("Error writing XLIFF file  ", e);
        }

        try (FileOutputStream fileStream = new FileOutputStream(file)) {
            XMLOutputFactory output = XMLOutputFactory.newInstance();
            XMLStreamWriter writer =
                    new IndentingXMLStreamWriter(
                            output.createXMLStreamWriter(fileStream, "utf-8"));
            try {
                writeHeader(writer, doc, locale);
                writeTransUnits(writer, doc, targetDoc, createSkeletons, approvedOnly);
                // end body tag
                writer.writeEndElement();
                // end file tag
                writer.writeEndElement();
                // end xliff tag
                writer.writeEndDocument();
                writer.flush();
            } finally {
                writer.close();
            }
        } catch (XMLStreamException | IOException e) {
            throw new RuntimeException("Error writing XLIFF file", e);
        }
    }

    /**
     * Used for writing source file
     *
     * @param baseDir
     * @param doc
     * @param locale
     *            (use hyphen, not underscore)
     */
    public static void write(File baseDir, Resource doc, String locale) {
        write(baseDir, doc, locale, null, true, false);
    }

}
